/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <ani.h>
#include <array>
#include <iostream>
#include <vector>

#include "aniutil/aniutil.h"

using namespace OHOS::AniUtil;

class CalcImpl {
public:
    int Sum(int a, int b) {
        std::cerr << "CalcImpl::Sum(" << a << ", " << b << ")" << std::endl;
        return a + b;
    }
};

enum Color: uint32_t
{
    Red,
    Green,
};

static ani_int Sum([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_object object, ani_int a, ani_int b)
{
    auto obj2 = ObjectFactory(env).Create("", "Lhello_ani/Calc;");
    (void)obj2;
    ObjectFactory(env).Create("", "", "Lhello_ani/Calc;");

    ani_class cls = nullptr;
    CalcImpl* nobj = new CalcImpl;
    auto obj = ObjectFactory(env).Create(cls);

    NativePtrWrapper wrapper(env, object);
    wrapper.Wrap<CalcImpl>(nobj);
    nobj = wrapper.Unwrap<CalcImpl>();
    (void)obj;

    UnionAccessor myobj2(env, object);
    expected<int, ani_status> result = myobj2.Convert<int>();
    if (result.has_value()) {
        double doubleValue = result.value();
        (void)doubleValue;
    }
    else {
        ani_status status = result.error();
        (void)status;
    }

    OptionalAccessor myopt(env, object);
    expected<bool, ani_status> boolOpt = myopt.Convert<bool>();
    if (boolOpt.has_value()) {
    }
    expected<double, ani_status> doubleOpt = myopt.Convert<double>();
    if (doubleOpt.has_value()) {
    }
    expected<std::string, ani_status> strOpt = myopt.Convert<std::string>();
    if (strOpt.has_value()) {
    }

    EnumAccessor colorAccessor(env);
    ani_enum_item item{};
    expected<Color, ani_status> color = colorAccessor.To<Color>(item);
    if (color) {
    }
    expected<int32_t, ani_status> intColor2 = colorAccessor.ToInt(item);
    if (intColor2) {
    }
    expected<std::string, ani_status> strColor2 = colorAccessor.ToString(item);
    if (strColor2) {
    }

    ani_namespace ns = nullptr;
    auto result3 = TypeFinder(env).FindEnum(ns, "LSensorAccuracy;");
    expected<ani_enum_item, ani_status> aniEnum = colorAccessor.From(result3.value(), 1);
    if (aniEnum.has_value()) {
    }

    std::vector<std::string> names;
    expected<ani_object, ani_status> objResult = ArrayAccessor<std::string>(env).Pack(names, FromString{});
    objResult = ArrayAccessor<std::string>(env).Pack(names.begin(), names.end(), FromString{});
    objResult = ArrayAccessor<std::string>(env).Pack(names.begin(), names.end(), [](ani_env *env, const std::string& str) -> expected<ani_object, ani_status> {
        ani_string aniStr;
        ani_status status = env->String_NewUTF8(str.c_str(), str.size(), &aniStr);
        if (ANI_OK != status) {
            return status;
        }
        return aniStr;
    });
    (void)objResult;
    std::vector<uint8_t> uint8Vec;
    expected<ani_object, ani_status> uint8ArrayResult = ArrayAccessor<uint8_t>(env).Pack(uint8Vec);
    uint8ArrayResult = ArrayAccessor<uint8_t>(env).Pack(uint8Vec);

    ani_status status;
    expected<std::size_t, ani_status> lenResult = ArrayAccessor<bool>(env, object).Length();
    (void)lenResult;
    expected<std::vector<bool>, ani_status> boolArrayResult = ArrayAccessor<bool>(env, object).Unpack(ToBoolean{});
    (void)boolArrayResult;
    std::vector<bool> boolArray2;
    status = ArrayAccessor<bool>(env, object).Unpack(std::back_inserter(boolArray2), ToBoolean{});
    std::vector<int> intArray2;
    status = ArrayAccessor<int>(env, object).Unpack(std::back_inserter(intArray2), ToInt{});
    std::vector<double> doubleArray2;
    status = ArrayAccessor<double>(env, object).Unpack(std::back_inserter(doubleArray2), ToDouble{});
    std::vector<std::string> strArray2;
    status = ArrayAccessor<std::string>(env, object).Unpack(std::back_inserter(strArray2), ToString{});
    status = ArrayAccessor<std::string>(env, object).Unpack(std::back_inserter(doubleArray2), [](ani_env *env, ani_object obj) -> expected<double, ani_status> {
        ani_double aniValue;
        ani_status status = env->Object_CallMethodByName_Double(obj, "unboxed", nullptr, &aniValue);
        if (ANI_OK != status) {
            return status;
        }
        return static_cast<double>(aniValue);
    });

    std::vector<uint8_t> uint8Array(8, 0);
    status = ArrayAccessor<uint8_t>(env, object).Unpack(std::back_inserter(uint8Array));
    status = ArrayAccessor<uint8_t>(env, object).Unpack(uint8Array.data(), uint8Array.size());
    expected<std::vector<uint8_t>, ani_status> uint8ArrayResult2 = ArrayAccessor<uint8_t>(env, object).Unpack();
    (void)uint8ArrayResult2;

    StdSharedPtrHolder<CalcImpl> *holder = new StdSharedPtrHolder<CalcImpl>(std::make_shared<CalcImpl>());
    holder->Get()->Sum(1, 2);
    holder->GetOrDefault()->Sum(1, 2);

    LocalScopeGuard localScopeGuard(env, 16);
    if (!localScopeGuard.IsStatusOK()) {
        return 0;
    }

    expected<std::unique_ptr<ScopedEnv>, ani_status> env2 = ScopedEnv::Create(env);
    std::unique_ptr<ScopedEnv> scopedAniEnv = std::move(env2.value());
    ani_env *scopedEnv = scopedAniEnv->GetEnv();
    (void)scopedEnv;

    auto guard = finally([] {
        std::cout << "Guarded cleanup" << std::endl;
    });

    expected<ani_error, ani_status> error = ErrorFactory(env).Create(1234, "Business error");
    (void)error;
    ErrorFactory(env).Throw(1234, "Business error");

    std::cerr << "calcObject: " << (void*)object << " in Sum" << std::endl;
    return 0;
};

ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result)
{
    std::cerr << "Enter ANI_Constructor" << std::endl;
    ani_env *env;
    if (ANI_OK != vm->GetEnv(ANI_VERSION_1, &env)) {
        std::cerr << "Unsupported ANI_VERSION_1" << std::endl;
        return (ani_status)9;
    }

    static const char *className = "Lhello_ani/Calc;";
    ani_class cls;
    if (ANI_OK != env->FindClass(className, &cls)) {
        std::cerr << "Not found '" << className << "'" << std::endl;
        return (ani_status)2;
    }

    std::array methods = {
        // ani_native_function {"init", nullptr, reinterpret_cast<void *>(Init)},
        ani_native_function {"sum", "II:I", reinterpret_cast<void *>(Sum)},
    };

    if (ANI_OK != env->Class_BindNativeMethods(cls, methods.data(), methods.size())) {
        std::cerr << "Cannot bind native methods to '" << className << "'" << std::endl;
        return (ani_status)3;
    };

    auto cleanerCls = TypeFinder(env).FindClass("Lhello_ani/Cleaner;");
    NativePtrCleaner(env).Bind(cleanerCls.value());

    *result = ANI_VERSION_1;
    return ANI_OK;
}
